A simple C# factory class
I have a project that is pretty small. Despite the small size, it is well-designed, using multiple layers, and interfaces, dependency injection, and unit tests. I need to create a production object at runtime and a mocked object at test time. I could easily use an IOC container. The problem with that is that most IOC containers (Autoface, Castle Windsor, Unity, etc.) are larger than my entire project. While I am a proponent of using IOC containers in large projects, I’m not a big proponent of using them in very small projects.
To make my code more unit testable, I am using a project called SystemWrapper that wraps standard system calls in an Interface and a Wrapper. Again, because my project is small, I didn’t bring in the SystemInterfaces and SystemWrapper dlls. This wrapper includes an ISmtpClient and an SmtpClientWrap object and I only brought in those two class files. The business logic uses the interface, ISmtpWrapper. This allows for me to unit test it by injecting a mock ISmtpClient.
I needed a simple factory that creates a new SmptClient in production runtime but allows for my unit test to create and use a mock ISmptClient during unit test time.
Here is what my factory should do:
Production
- Create a new SmptClientWrap object (which wraps an System.Net.SmptClient oject).
- Use setting from the app.config or web.config for the mail server, domain, user, and password.
Unit Test
- Create an mock of ISmptClient (using Moq).
Here is the simple factory class that I wrote:
using System.Configuration; using System.Net; using SystemInterface.Net.Mail; using SystemWrapper.Net.Mail; namespace Rhyous.System.Factory { public class SmtpClientFactory { public ISmtpClient GetNewSmtpClient() { return CreateCredentialsMethod(); } public delegate ISmtpClient CreateCredentialsDelegate(); public CreateCredentialsDelegate CreateCredentialsMethod = () => new SmtpClientWrap(ConfigurationManager.AppSettings["SmtpServer"]) { Credentials = new NetworkCredential { Domain = ConfigurationManager.AppSettings["SmtpDomain"], UserName = ConfigurationManager.AppSettings["SmtpUser"], Password = ConfigurationManager.AppSettings["SmtpPassword"] } }; } }
In the above class, the GetNewSmptClient returns an ISmtpClient. I use a delegate to create a concrete ISmtpClient called SmtpClienWrap. The default delegate implementation gets the data from the app.config or web.config.
Now I can inject a concrete ISmtpClient into my code:
using (var smtpClient = SmtpClientFactory.GetNewSmtpClient()) { var mailer = new Mailer(smtpClient); }
Note: I could make SmptClientFactory static or make it a singleton. I’m thinking about both.
Now in a test, I am able to create a mock ISmtpClient. Here is an example.
[TestMethod] public void ReplacingTheCreateCredentialsDelegateWorks() { var factory = new SmtpClientFactory(); bool _wasCalled = false; factory.CreateCredentialsMethod = () => { _wasCalled = true; return new Mock<ISmtpClient>().Object; }; var client = factory.GetNewSmtpClient(); Assert.IsTrue(_wasCalled); }
The one problem with my factory is that it is pretty specific to one class. It might be interesting to make it more generic.
using System; namespace Rhyous.Factory { public class ObjectFactory<TInterface, TObject> where TInterface : class where TObject : TInterface, new() { public TInterface GetNewObject() { if (!typeof(TInterface).IsInterface) { throw new Exception("The first generic, TInterface, must be an interface."); } return CreateObjectMethod(); } public delegate TInterface CreateObjectDelegate(); public virtual CreateObjectDelegate CreateObjectMethod { get { return _CreateObjectMethod ?? (_CreateObjectMethod = () => Activator.CreateInstance<TObject>()); } set { _CreateObjectMethod = value; } } public CreateObjectDelegate _CreateObjectMethod; } }
Now I move my delegate (which is creation method of the factory) upstream to where I instantiate the factory.
var smtpClientFactory = smtpClientFactory = new ObjectFactory<ISmtpClient, SmtpClientWrap> { CreateObjectMethod = () => new SmtpClientWrap(ConfigurationManager.AppSettings["SmtpServer"]) { Credentials = new NetworkCredential { Domain = ConfigurationManager.AppSettings["SmtpDomain"], UserName = ConfigurationManager.AppSettings["SmtpUser"], Password = ConfigurationManager.AppSettings["SmtpPassword"] } } }; // Then later use the factory... using (var smtpClient = smtpClientFactory.GetNewSmtpClient()) { var mailer = new Mailer(smtpClient); }
Anyway, have fun with this mini-factory. It might be useful on small projects where you don’t want an entire IOC container.